package top.maof.mfcoin.tx;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ByteUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import lombok.Data;
import top.maof.mfcoin.algo.SecretUtil;

import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Base64;
import java.util.List;
import java.util.UUID;

/**
 * 交易
 * 为简化流程，一次交易只能有一个输入，多个输出
 * 输出的地址必须唯一，不能出现一次交易中地址重复的现象
 * 为避免小数,所有涉及到币的交易一律用int类型
 * <p>
 * <p>
 * 交易若是合法,合法交易之间不会出现hash碰撞
 * 除非出现double spending攻击
 */
@Data
public class Transaction {
    // 转账公钥
    private byte[] publicKey;

    // 该交易是否为铸币交易
    private boolean isCoinBase;

    private Input input;

    private List<Output> outputs;

    // 时间戳
    private long time = System.currentTimeMillis();

    // 通过UUID保证hash唯一
    private String txid = UUID.randomUUID().toString().replace("-", "");


    // 哈希,交易唯一性由hash体现，base64编码
    private String hash;

    /**
     * 签名的base64编码
     * <p>
     * 签名问题：
     * 同一个私钥，信息不一样，签名一定不一样 ?
     * 不同私钥,签名一定不一样 ?
     */
    private String sign;


    /**
     * Transaction求hash
     * 求hash时需排除hash和sign字段
     * 并对摘要字节数组进行base64编码
     */
    public static String hashTransaction(Transaction tx) {
        byte[] hash = hash(tx);

        byte[] digest = null;
        try {
            digest = SecretUtil.digest(SecretUtil.DIGEST_ALGORITHM_SHA256, hash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return Base64.getEncoder().encodeToString(digest);
    }


    public static byte[] hash(Transaction tx) {
        byte[] txBytes = tx.publicKey;

        txBytes = ArrayUtil.addAll(txBytes, ByteUtil.intToBytes(tx.isCoinBase ? 1 : 0));

        if (tx.input != null) {
            txBytes = ArrayUtil.addAll(txBytes, Input.hashInput(tx.input));
        }

        for (Output output : tx.outputs) {
            txBytes = ArrayUtil.addAll(txBytes, Output.hashOutput(output));
        }

        txBytes = ArrayUtil.addAll(txBytes, ByteUtil.longToBytes(tx.time), tx.txid.getBytes());


        byte[] digest = null;
        try {
            digest = SecretUtil.digest(SecretUtil.DIGEST_ALGORITHM_SHA256, txBytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return digest;
    }


    /**
     * 返回base64编码的签名
     * 对Transaction对象的json字符串的hash值进行签名，并且将生成
     * 签名转化为base64编码字符串，得到base64签名字符串
     *
     * @author 毛逢
     * @date 2021/11/11 15:40
     */
    public static String sign(PrivateKey privateKey, Transaction transaction) throws Exception {
        byte[] sign = SecretUtil.sign(privateKey, hash(transaction));
        return Base64.getEncoder().encodeToString(sign);
    }

    /**
     * 验证签名
     * 与方法sign()处理的方式相反即可，需一一对应
     *
     * @author 毛逢
     * @date 2021/11/11 15:43
     */
    public static boolean verify(Transaction tx) {

        String hash = hashTransaction(tx);

        if (!hash.equals(tx.getHash())) {
            return false;
        }

        PublicKey publicKey = Wallet.publicKey(tx.publicKey);

        return SecretUtil.verify(publicKey, Base64.getDecoder().decode(hash),
                Base64.getDecoder().decode(tx.getSign()));

    }


}
